﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IO;
using System.Web.UI.WebControls;
using System.Xml;
using System.Text.RegularExpressions;
using System.Security.Principal;
using System.Web.Hosting;
using System.Net;
using System.Xml.Schema;
using System.Xml.Linq;
using HtmlAgilityPack;

namespace SimpleSocxs
{
    [Serializable]
    public class User : IPrincipal
    {
        public int ID { get; set; }
        public string Name { get; set; }

        public IIdentity Identity
        {
            get { return new GenericIdentity(Name); }
        }

        public bool IsInRole(string role)
        {
            return true;
        }
    }
    [Serializable]
    public class Page
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Content { get; set; }
        public int UserID { get; set; }

        public string ContentWithStreamPanels
        {
            get { return Helper.InjectStreamPanels(Content); }
        }
    }

    [Serializable]
    public class SocxsDataStore
    {
        internal static SocxsDataStore _current = new SocxsDataStore();
        public static SocxsDataStore Current { get { return _current; } }
        List<User> users = new List<User>();
        List<Page> pages = new List<Page>();
        List<SocialStream> streams = new List<SocialStream>();

        public int AddUser(User user)
        {
            if (GetUserByName(user.Name) != null)
                throw new Exception("User exists");

            var new_path = Helper.MapPath("~/users/" + user.Name);
            if (!Directory.Exists(new_path))
            {
                Directory.CreateDirectory(new_path);
                Directory.CreateDirectory(new_path + "/assets");
                Directory.CreateDirectory(new_path + "/renders");
                Directory.CreateDirectory(new_path + "/templates");
            }

            users.Add(user);
            return user.ID = users.Max(_ => _.ID) + 1;
        }

        public int AddPage(int userID, Page page)
        {
            if (GetAllUserPages(userID).Any(_ => _.Name == page.Name))
                throw new Exception("Page exists");

            page.UserID = userID;

            var page_path = Helper.MapPath("~/users/" + GetUserByID(userID).Name + "/" + page.Name + ".master");
            File.WriteAllText(page_path, page.ContentWithStreamPanels);

            pages.Add(page);
            return page.ID = pages.Max(_ => _.ID) + 1;
        }

        public int AddStream(int userID, SocialStream stream)
        {
            if (GetAllUserStreams(userID).Any(_ => _.Name == stream.Name))
                throw new Exception("Stream exists");

            stream.UserID = userID;
            streams.Add(stream);

            return stream.ID = streams.Max(_ => _.ID) + 1;
        }

        public void DeleteStream(int streamID)
        {
            var stream = GetStreamByID(streamID);
            streams.Remove(stream);
        }

        public int AddPageFromTemplate(int userID, string pageName, string templateName)
        {
            if (string.IsNullOrEmpty(pageName))
                throw new ArgumentNullException("pageName");

            var user = GetUserByID(userID);
            var filePath = Helper.GetFileInUserAndShared(user.Name, "templates/" + templateName + ".master");
            
            var template_content = File.ReadAllText(Helper.MapPath(filePath));

            var page = new Page { Content = template_content, Name = pageName };
            return AddPage(userID, page);
        }

        public void UpdatePage(int userID, Page page)
        {
            var page_path = Helper.MapPath("~/users/" + GetUserByID(userID).Name + "/" + page.Name + ".master");
            File.WriteAllText(page_path, page.ContentWithStreamPanels);
        }

        public void UpdatePage(Page newPage)
        {
            var old = GetPageByID(newPage.ID);
            old.Name = newPage.Name;
            old.Content = newPage.Content;
            var page_path = Helper.MapPath("~/users/" + GetUserByID(newPage.UserID).Name + "/" + newPage.Name + ".master");
            File.WriteAllText(page_path, newPage.ContentWithStreamPanels);
        }

        public void UpdatePageStreamPanels(int pageID, string streamPanelsInnerHtml)
        {            
            var page = GetPageByID(pageID);

            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(page.Content);
            doc.DocumentNode.SelectSingleNode("//div[contains(@class,'socxs_panels')]").InnerHtml = streamPanelsInnerHtml;            

            page.Content = doc.GetHtml();

            var page_path = Helper.MapPath("~/users/" + GetUserByID(page.UserID).Name + "/" + page.Name + ".master");
            File.WriteAllText(page_path, page.ContentWithStreamPanels);
        }

        public void UploadAsset(int userID, FileUpload fup)
        {
            var user = GetUserByID(userID);
            fup.SaveAs(Helper.MapPath("~/users/" + user.Name + "/assets/" + fup.FileName));
        }

        public string[] GetAssets(int userID)
        {
            return Directory.GetFiles(Helper.MapPath("~/users/" + GetUserByID(userID).Name) + "/assets")
                .Select(_ => Path.GetFileName(_))
                .ToArray();
        }

        public void DeletePage(int pageID)
        {
            var page = GetPageByID(pageID);
            var user = GetUserByID(page.UserID);

            File.Delete(Helper.MapPath("~/users/" + user.Name + "/" + page.Name + ".master"));
            pages.Remove(page);
        }

        public void DeleteAsset(int userID, string filename)
        {
            var user = GetUserByID(userID);
            File.Delete(Helper.MapPath("~/users/" + user.Name + "/assets/" + filename));
        }       

        public User GetUserByID(int id)
        {
            return users.SingleOrDefault(_ => _.ID == id);
        }

        public Page GetPageByID(int id)
        {
            return pages.SingleOrDefault(_ => _.ID == id);
        }

        public SocialStream GetStreamByID(int id)
        {
            return streams.SingleOrDefault(_ => _.ID == id);
        }

        public IEnumerable<User> GetAllUsers()
        {
            return users;
        }

        public IEnumerable<Page> GetAllUserPages(int userID)
        {
            return pages.Where(_ => _.UserID == userID);
        }

        public IEnumerable<SocialStream> GetAllUserStreams(int userID)
        {
            return streams.Where(_ => _.UserID == userID);
        }

        public User GetUserByName(string name)
        {
            return users.SingleOrDefault(_ => _.Name == name);
        }
    }

    public enum StreamType
    {
        Twitter,
        Blogger,
        Youtube,
        Flickr,
    }

    [Serializable]
    public class SocialStream
    {
        public SocialStream() { }
        public SocialStream(string name, StreamType type)
        {
            this.Name = name;
            this.StreamType = type;
        }

        public int ID { get; set; }
        public string Name { get; set; }
        public string Url { get; set; }
        public string RawData { get; set; }
        public StreamType StreamType { get; set; }
        public string Options { get; set; }
        public DateTime? UpdatedOn { get; set; }
        public int UserID { get; set; }

        public string GetDefaultRenderName()
        {
            return "Default" + StreamType + "Render";
        }

        public IStreamDataConverter GetConverter()
        {
            return Activator.CreateInstance(Type.GetType("SimpleSocxs." + StreamType + "DataConverter")) as IStreamDataConverter;            
        }

        public IStreamExtractor GetExtractor()
        {
            return Activator.CreateInstance(Type.GetType("SimpleSocxs." + StreamType + "Extractor")) as IStreamExtractor;
        }

        public SocialExtractResult Extract()
        {
            return Extract(null);
        }

        public SocialExtractResult Extract(string rawUrl)
        {
            var data = string.Empty;
            try
            {                
                var extractor = this.GetExtractor();
                var converter = this.GetConverter();
                if (!string.IsNullOrEmpty(rawUrl))
                    this.Url = extractor.PrepareUrl(rawUrl);
                data = extractor.Extract(this);
                var converted_data = converter.ConvertFrom(data);

                this.UpdatedOn = DateTime.Now;
                this.RawData = data;

                return new SocialExtractResult { Success = true, ReturnedData = data, ConvertedData = converted_data };
            }
            catch (Exception ex)
            {
                return new SocialExtractResult { Success = false, Error = ex, ReturnedData = data };
            }
        }
    }
    
    public class SocialExtractResult
    {
        public string ReturnedData { get; set; }
        public object ConvertedData { get; set; }
        public bool Success { get; set; }
        public Exception Error { get; set; }
    }

    public interface IStreamDataConverter
    {
        object ConvertFrom(string raw);               
    }

    public class TwitterDataConverter : IStreamDataConverter
    {
        public object ConvertFrom(string raw)
        {
            var result = new List<TwitterData>();
            var doc = new XmlDocument();
            doc.LoadXml(raw);
            var xpath = doc.CreateNavigator();

            XmlNodeList statuses = doc.SelectNodes("/statuses/status");
            foreach (XmlNode status in statuses)
            {
                var data = new TwitterData();

                data.Content = status.SelectSingleNode("text").InnerXml;
                data.CreatedOn = Helper.ParseTwitterDateTime(status.SelectSingleNode("created_at").InnerXml);
                data.ProfileImageUrl = status.SelectSingleNode("user/profile_image_url").InnerXml;
                data.UserName = status.SelectSingleNode("user/name").InnerXml;
                data.ScreenName = status.SelectSingleNode("user/screen_name").InnerXml;
                data.UserID = status.SelectSingleNode("user/id").InnerXml;

                result.Add(data);
            }

            return result;
        }
    }

    public class BloggerDataConverter : IStreamDataConverter
    {
        const int MAX_WORDS = 200;
        public object ConvertFrom(string raw)
        {
            var result = new List<BloggerData>();            
            var doc = new XmlDocument();
            //trick to remove default namespace to ease process
            doc.LoadXml(raw.Replace("http://www.w3.org/2005/Atom", string.Empty));
            var xpath = doc.CreateNavigator();            
            
            var author = xpath.SelectSingleNode("/feed/author");
            var author_name = author.SelectSingleNode("name").InnerXml;
            
            XmlNodeList entries = doc.SelectNodes("/feed/entry");
            foreach (XmlNode entry in entries)
            {
                var data = new BloggerData();
                data.AuthorName = author_name;                
                data.UpdatedOn = DateTime.Parse(entry.SelectSingleNode("updated").InnerXml);
                data.Title = entry.SelectSingleNode("title").InnerXml;
                data.Url = entry.SelectSingleNode("link[@rel='alternate']/@href").Value;

                var summary_node = entry.SelectSingleNode("summary");
                if (summary_node != null)
                {
                    data.Content = summary_node.InnerXml;
                }
                else
                {
                    var content_node = entry.SelectSingleNode("content");
                    if (content_node != null)
                    {
                        var content = HttpUtility.HtmlDecode(Helper.RemoveAllTags(HttpUtility.HtmlDecode(entry.SelectSingleNode("content").InnerXml)));
                        if (content.Length > MAX_WORDS)
                            content = content.Substring(0, MAX_WORDS) + " ...";
                        data.Content = content;
                    }
                }

                result.Add(data);
            }

            return result;
        }
    }

    public class FlickrDataConverter : IStreamDataConverter
    {
        public object ConvertFrom(string raw)
        {
            var result = new List<FlickrData>();
            var doc = new XmlDocument();
            doc.LoadXml(raw);
            var xpath = doc.CreateNavigator();

            XmlNodeList photos = doc.SelectNodes("//photo");
            foreach (XmlNode photo in photos)
            {
                var data = new FlickrData();
                data.UserID = photo.Attributes["owner"].Value;
                data.Title = HttpUtility.HtmlDecode(photo.Attributes["title"].Value);
                data.ImageID = photo.Attributes["id"].Value;
                data.Server = photo.Attributes["server"].Value;
                data.Secret= photo.Attributes["secret"].Value;
                data.Farm = photo.Attributes["farm"].Value;
                result.Add(data);
            }

            return result;
        }
    }

    public class YoutubeDataConverter : IStreamDataConverter
    {
        public object ConvertFrom(string raw)
        {
            var result = new List<YoutubeData>();

            var root = XElement.Parse(raw);
            XNamespace xmlns = "http://www.w3.org/2005/Atom";
            XNamespace media = "http://search.yahoo.com/mrss/";
            var entries = root.Descendants(xmlns + "entry");
            foreach (var entry in entries)
            {
                try
                {
                    var data = new YoutubeData();
                    data.PublishedOn = DateTime.Parse(entry.Element(xmlns + "published").Value);
                    data.UpdatedOn = DateTime.Parse(entry.Element(xmlns + "updated").Value);
                    data.Title = entry.Element(xmlns + "title").Value;
                    data.AuthorName = entry.Element(xmlns + "author").Element(xmlns + "name").Value;
                    var group = entry.Element(media + "group");
                    data.PlayerUrl = group.Element(media + "player").Attribute("url").Value;
                    data.ThumbnailUrl = group.Element(media + "thumbnail").Attribute("url").Value;
                    result.Add(data);
                }
                catch(Exception ex)
                {
                    //log
                }
            }

            return result;
        }
    }

    public class TwitterData
    {
        public string UserID { get; set; }
        public string UserName { get; set; }
        public string UserUrl
        {
            get
            {
                return "http://twitter/" + ScreenName;
            }
        }
        public string ProfileImageUrl { get; set; }
        public string Content { get; set; }
        public DateTime CreatedOn { get; set; }

        public string ScreenName { get; set; }
    }

    public class BloggerData
    {
        public string Title { get; set; }
        public string Url { get; set; }
        public DateTime UpdatedOn { get; set; }
        public string AuthorName { get; set; }
        public string ProfileUrl
        {
            get
            {
                return "http://" + AuthorName + ".blogspot.com/";
            }
        }
        public string Content { get; set; }
    }

    public class FlickrData
    {        
        public string UserID { get; set; }
        public string Title { get; set; }
        public string ImageID { get; set; }
        public string Secret { get; set; }
        public string Server { get; set; }
        public string Farm { get; set; }
        public string ImageUrl
        {
            get
            {
                return string.Format("http://farm{0}.static.flickr.com/{1}/{2}_{3}.jpg", Farm, Server, ImageID, Secret);
            }
        }
        public string PostUrl
        {
            get
            {
                return string.Format("http://www.flickr.com/photos/{0}/{1}", UserID, ImageID);
            }
        }
    }

    public class YoutubeData
    {
        public DateTime PublishedOn { get; set; }
        public DateTime UpdatedOn { get; set; }
        public string Title { get; set; }
        public string AuthorName { get; set; }
        public string PlayerUrl { get; set; }
        public string ThumbnailUrl { get; set; }
    }

    public interface IStreamExtractor
    {
        string Extract(SocialStream stream);
        string PrepareUrl(string str);
    }

    public abstract class BaseExtractor
    {
        protected string ValidateUserName(string username)
        {
            if (!Regex.Match(username = username.Trim(), @"^\w+$").Success)
                throw new ArgumentException("Invalid username");
            return username;
        }

        public virtual string Extract(SocialStream stream)
        {            
            //todo:deal with options
            var url = stream.Url;
            //stream.UpdatedOn = DateTime.Now;
            using (WebClient wc = new WebClient())
            {                
                return wc.DownloadString(url);
            }            
        }
    }

    public class BloggerExtractor : BaseExtractor, IStreamExtractor
    {        
        public string PrepareUrl(string username)
        {
            username = base.ValidateUserName(username);
            return string.Format("http://{0}.blogspot.com/feeds/posts/default", username);
        }
    }

    public class TwitterExtractor : BaseExtractor, IStreamExtractor
    {
        public string PrepareUrl(string username)
        {
            username = base.ValidateUserName(username);

            return string.Format("http://twitter.com/statuses/user_timeline.xml?screen_name=" + username); 
        }       
    }

    public class FlickrExtractor : BaseExtractor, IStreamExtractor
    {
        public string PrepareUrl(string username)
        {
            username = base.ValidateUserName(username);

            const string api_key = "345a5771d77dccae78dd45f0be72f2f5";
            
            string response = string.Empty;
            using (WebClient wc = new WebClient())
            {

                 response = wc.DownloadString(string.Format(
@"http://api.flickr.com/services/rest/?method=flickr.urls.lookupUser&url=http://www.flickr.com/photos/{0}&api_key={1}", username, api_key));
            }

            var doc = new XmlDocument();
            doc.LoadXml(response);

            var xpath = doc.CreateNavigator();
            var id = xpath.SelectSingleNode("//user/@id").Value;

            return string.Format(
@"http://api.flickr.com/services/rest/?method=flickr.photos.search&user_id={0}&api_key={1}", id, api_key);
        }
    }

    public class YoutubeExtractor : BaseExtractor, IStreamExtractor
    {
        public string PrepareUrl(string username)
        {
            username = base.ValidateUserName(username);

            return string.Format("http://gdata.youtube.com/feeds/api/users/{0}/uploads", username);
        }
    }           
}